/**
 * @description:
 * @file: class.ts
 * @author: Evan
 * @date: 2025.07.16 17:31:03
 * @week: 周三
 * @version: V
 */

import * as MD from './data/methods'
import { TransformControls } from 'three/examples/jsm/controls/TransformControls.js'
import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min.js'
import { Message } from '@/utils/element'

import { main } from './modules/model'
import { pointSphere } from './modules/point-sphere'
import { transfControls } from './modules/transform-controls'
import { material } from './modules/material'
import { light } from './modules/light'
import { meshName } from './modules/mesh-name'
import { text } from './modules/text'
import { reset } from './modules/reset'
import { env } from './modules/env'

import type { ExtendOptions } from './data/type'
import { modelOpts, resetOpts } from './modules/store'

const { THREE, Utils } = MD

const { raycaster, pointer, update: raycasterUpdate } = MD.Hooks.useRaycaster()

const options = {
  // 缩放
  scale: 1,
  // 双面
  side: false,
  // 入场动画
  insetAnimate: false,
  // 转换材质
  transformMaterial: false,
  // 地面反光
  groundReflection: false,
  // 材质反光
  glisten: false,
  // 透明外壳
  opacitySkin: false,
  opacity: 0.6
}

export class Scene extends MD.Scene {
  // 扩展参数
  extend: Partial<ExtendOptions>

  // 模型组
  modelGroup?: InstanceType<typeof THREE.Group>
  // 辅助组
  helperGroup?: InstanceType<typeof THREE.Group>

  gui: InstanceType<typeof GUI>
  guiOpts = options

  // 地面网格
  groundMesh?: InstanceType<typeof THREE.Mesh>

  // 定位球
  setPiece: InstanceType<typeof THREE.Mesh>

  // 水面
  water?: ReturnType<typeof MD.createWater>

  // 文本
  text?: ReturnType<typeof MD.createText>

  // 转换控制器
  transformControls?: InstanceType<typeof TransformControls>

  // 模块
  public modules: {
    mian?: ReturnType<typeof main>
    pointSphere?: ReturnType<typeof pointSphere>
    tsControls?: ReturnType<typeof transfControls>
    material?: ReturnType<typeof material>
    light?: ReturnType<typeof light>
    meshName?: ReturnType<typeof meshName>
    text?: ReturnType<typeof text>
    reset?: ReturnType<typeof reset>
    env?: ReturnType<typeof env>
  } = {}

  constructor(options: ConstructorParameters<typeof MD.Scene>[0], extend: Partial<ExtendOptions>) {
    super(options)
    this.extend = extend

    this.createClock()

    // 注册模块
    this.modules.mian = main(this)
    this.modules.pointSphere = pointSphere(this)
    this.modules.tsControls = transfControls(this)
    this.modules.material = material(this)
    this.modules.light = light(this)
    this.modules.meshName = meshName(this)
    this.modules.text = text(this)
    this.modules.reset = reset(this)
    this.modules.env = env(this)

    // 模型组
    this.modules.mian.addModelGroup()

    // 控制器
    this.modules.tsControls.add()

    // 点位球
    this.setPiece = MD.createPointSphere()
    this.setPiece.visible = false
    this.addObject(this.setPiece)

    this.addGround()

    this.gui = new GUI()
    this.addGui()

    this.bindEvent()

    // 重置配置
    resetOpts()
  }

  addGui() {
    const gui = this.gui

    // 主功能
    this.modules.mian?.add()

    // 点位球
    this.modules.pointSphere?.add()

    // 材质处理
    this.modules.material?.add()

    // 灯光处理
    this.modules.light?.add()

    // 网格名称处理
    this.modules.meshName?.add()

    // 文本处理
    this.modules.text?.add()

    // 重置
    this.modules.reset?.add()

    // 环境
    this.modules.env?.add()

    gui.domElement.className += ' gui-wrap'
    this.container.parentNode?.appendChild(gui.domElement)
  }

  // 复制文本框
  async copyTextarea(msg = '复制成功！') {
    // @ts-ignore
    const textareaEl = this.container.parentNode.querySelector(
      '.gui-text textarea'
    ) as HTMLInputElement

    await nextTick()
    textareaEl.select()
    document.execCommand('copy')
    Message(msg, 'success')
  }

  // 相机视角重置
  cameraViewReset() {
    Utils.cameraInSceneAnimate(
      this.camera,
      {
        x: 0,
        y: 100,
        z: 300
      },
      this.controls?.target
    )
  }

  // 添加地面
  addGround() {
    const ground = this.createGround(5000, 5000, 0xffffff)
    ground.name = 'ground'
    ground.visible = false
    this.groundMesh = ground
    this.addObject(ground)
  }

  modelAnimate() {
    if (!this.modelGroup) return

    let delta = this.clock?.getDelta()
    for (let i = 0; i < this.modelGroup.children.length; i++) {
      const model = this.modelGroup.children[i]
      if (model.userData.__mixer__) {
        model.userData.__mixer__.update(delta)
      }
    }

    // 水面波动
    if (this.water) {
      this.water.material.uniforms['time'].value += 1 / 60
    }
  }

  // 移动
  onPointerMove(e: PointerEvent) {
    this.checkIntersectObjects(e)
  }

  // 弹起
  onPointerUp(e: PointerEvent) {
    super.onPointerUp(e)

    let s = e.timeStamp - this.pointer.tsp
    // 判断是否未点击
    const isClick = s < 200
    if (isClick) {
      const dom = this.container
      const scale = this.options.scale

      // 获取元素偏移量
      const rect = dom.getBoundingClientRect() || { left: 0, top: 0 }
      // 渲染元素作为子组件可能有缩放处理，元素大小需要计算处理
      // 设置二维向量坐标 （-1， 1 范围）
      const onUpPosition = new THREE.Vector2(
        ((e.clientX - rect.left) / (dom.clientWidth * scale)) * 2 - 1,
        -((e.clientY - rect.top) / (dom.clientHeight * scale)) * 2 + 1
      )

      // 判断指针按下与弹起的距离是否为0 则解除对象
      if (pointer.distanceTo(onUpPosition) == 0) this.transformControls?.detach()
    }
    if (e.button == 2) {
      // console.log('你点了右键')
    } else if (e.button == 0) {
      // console.log('你点了左键')
      isClick && this.checkIntersectObjects(e)
    } else if (e.button == 1) {
      // console.log('你点了滚轮')
    }
  }

  // 检查交叉几何体
  checkIntersectObjects(e: PointerEvent) {
    const dom = this.container
    const scale = this.options.scale
    raycasterUpdate(e, dom, scale)

    let isClick = e.type == 'pointerdown' || e.type == 'pointerup'

    const pickDot = this.setPiece.visible
    // 计算对象列表
    const objects = isClick ? this.modelGroup?.children || [] : pickDot ? [this.setPiece] : []
    // 设置新的原点和方向向量更新射线, 用照相机的原点和点击的点构成一条直线
    raycaster.setFromCamera(pointer, this.camera)

    let intersects = raycaster.intersectObjects(objects, isClick /* 是否检查所有后代 */)
    if (intersects.length > 0) {
      const intersect = intersects[0]
      let object = intersect.object
      if (isClick) {
        console.log(intersect)
        this.setPiece.position.copy(intersect.point)

        if (!pickDot) {
          this.modules.mian?.selectModel({ uuid: object.uuid })
          modelOpts.uuid = object.uuid

          this.modules.tsControls?.setObject(object)
        } else {
          const dotText = [this.setPiece].map(el => {
            let p = <any>el.position
            return `{ "x": ${p.x.toFixed(2) * 1}, "y": ${p.y.toFixed(2) * 1}, "z": ${
              p.z.toFixed(2) * 1
            } }`
          })
          modelOpts.dotText = dotText.join(',')
          this.copyTextarea('中心点复制成功！')
        }
      } else {
        this.modules.tsControls?.setObject(object)
      }
    }
  }
}
